#region Using Statements
using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Serialization;
using OpenComposite.EII.Forms;
using OpenComposite.Base.Collections;
using OpenComposite.EII.Repository;
#endregion

namespace OpenComposite.EII.Designer.Workflow
{
	internal class EventBindingService : System.ComponentModel.Design.EventBindingService, IEventBindingService
	{
		private WorkflowLoader loader;
		private IServiceProvider serviceProvider;

		public EventBindingService(IServiceProvider provider, WorkflowLoader loader)
			: base(provider)
		{
			this.loader = loader;
			this.serviceProvider = provider;
		}
		protected override string CreateUniqueMethodName(IComponent component, EventDescriptor e)
		{
			return e.DisplayName;
		}

		protected override ICollection GetCompatibleMethods(EventDescriptor e)
		{
			ArrayList methodNames = new ArrayList();
			CodeCompileUnit ccu = this.loader.CodeBesideCCU;
			MethodInfo mi = e.EventType.GetMethod("Invoke");
			foreach ( CodeTypeMember member in ccu.Namespaces[0].Types[0].Members ) {
				CodeMemberMethod method = member as CodeMemberMethod;
				if ( method != null ) {
					bool sameReturnType =
						( method.ReturnType != null && method.ReturnType.BaseType == mi.ReturnType.FullName );
					bool sameParameters = false;
					ParameterInfo[] pinfos = mi.GetParameters();
					if ( pinfos.Length == method.Parameters.Count ) {
						sameParameters = true;
						for ( int i = 0; i < pinfos.Length; i++ ) {
							if ( pinfos[i].ParameterType.FullName != method.Parameters[i].Type.BaseType ) {
								sameParameters = false;
								break;
							}
						}
					}
					if ( sameReturnType && sameParameters ) {
						methodNames.Add(method.Name);
					}
				}
			}

			return methodNames;
		}

		protected override bool ShowCode()
		{
			return false;
		}

		protected override bool ShowCode(int lineNumber)
		{
			return false;
		}

		public bool ShowCode(IComponent component, EventDescriptor e)
		{
			return ShowCode(component, e, null);
		}

		protected override void UseMethod(IComponent component, EventDescriptor e, string methodName)
		{
			if ( !String.IsNullOrEmpty(methodName) ) {
				Generate(component, e, methodName, true);
			}
		}

		protected override void FreeMethod(IComponent component, EventDescriptor e, string methodName)
		{
			if ( !String.IsNullOrEmpty(methodName) ) {
				Generate(component, e, methodName, false);
			}
		}

		/// <summary>
		/// Generate the method and add to code compile unit
		/// </summary>
		/// <param name="component"></param>
		/// <param name="e"></param>
		/// <param name="methodName"></param>
		/// <param name="addMethod"></param>
		private void Generate(IComponent component, EventDescriptor e, string methodName, bool addMethod)
		{
			TypeProvider typeProvider = (TypeProvider)this.serviceProvider.GetService(typeof(ITypeProvider));
			CodeCompileUnit ccu = this.loader.CodeBesideCCU;

			Type delegateType = e.EventType;
			MethodInfo invokeInfo = delegateType.GetMethod("Invoke");

			Type returnType = invokeInfo.ReturnType;

			MemberAttributes modifiers = MemberAttributes.Private;
			PropertyDescriptor modifiersProp = TypeDescriptor.GetProperties(component)["DefaultEventModifiers"];
			if ( modifiersProp != null && modifiersProp.PropertyType == typeof(MemberAttributes) ) {
				modifiers = (MemberAttributes)modifiersProp.GetValue(component);
			}

			CodeParameterDeclarationExpressionCollection paramCollection = new CodeParameterDeclarationExpressionCollection();
			foreach ( ParameterInfo paramInfo in invokeInfo.GetParameters() ) {
				CodeParameterDeclarationExpression codeParamExpression = new CodeParameterDeclarationExpression();
				codeParamExpression.Name = paramInfo.Name;
				codeParamExpression.Type = new CodeTypeReference(paramInfo.ParameterType);

				paramCollection.Add(codeParamExpression);
			}

			// create code member method
			CodeMemberMethod method = new CodeMemberMethod();
			method.Name = methodName;
			method.Parameters.AddRange(paramCollection);
			method.ReturnType = new CodeTypeReference(returnType);
			method.Attributes = modifiers;

			if ( addMethod ) {
				CodeMemberMethod codeMethod = findMethod(ccu, component, e, method.Name);
				if ( codeMethod == null ) {
					// The assumption here is that we have only one namespace and type being designed
					ccu.Namespaces[0].Types[0].Members.Add(method);
					Repository.Workflow.MethodCodeSnippet mcs = new OpenComposite.EII.Repository.Workflow.MethodCodeSnippet();
					OpenComposite.Workflow.ExtendedCodeActivity code = component as OpenComposite.Workflow.ExtendedCodeActivity;
					if ( code != null ) {
						mcs.ActivityName = code.Name;
					}
					mcs.MethodName = method.Name;
					mcs.ReturnType = method.ReturnType.BaseType;
					foreach ( CodeParameterDeclarationExpression par in method.Parameters ) {
						Repository.Workflow.MethodParameter mp = new OpenComposite.EII.Repository.Workflow.MethodParameter();
						mp.Name = par.Name;
						mp.Type = par.Type.BaseType;
						mcs.MethodParameters.Add(mp);
					}
					loader.Workflow.ExtendedProperties.MethodCodeSnippets.Add(mcs);
				}
			} else {
				if ( ccu.Namespaces[0].Types[0].Members.Contains(method) ) {
					ccu.Namespaces[0].Types[0].Members.Remove(method);
				} else {
					foreach ( CodeTypeMember member in ccu.Namespaces[0].Types[0].Members ) {
						if ( member is CodeMemberMethod && member.Name == method.Name ) {
							ccu.Namespaces[0].Types[0].Members.Remove(member);
							break;
						}
					}
				}
				foreach ( Repository.Workflow.MethodCodeSnippet mcs in loader.Workflow.ExtendedProperties.MethodCodeSnippets ) {
					if ( mcs.MethodName == method.Name ) {
						loader.Workflow.ExtendedProperties.MethodCodeSnippets.Remove(mcs);
						break;
					}
				}
			}
			typeProvider.RefreshCodeCompileUnit(this.loader.CodeBesideCCU, new EventHandler(RefreshCCU));
		}

		private void RefreshCCU(object sender, EventArgs e)
		{
			//We dont want to do anything here
		}

		private CodeMemberMethod findMethod(CodeCompileUnit ccu, IComponent component, EventDescriptor e)
		{
			return findMethod(ccu, component, e);
		}

		private CodeMemberMethod findMethod(CodeCompileUnit ccu, IComponent component, EventDescriptor e, string methodName)
		{
			Activity act = component as Activity;
			if ( string.IsNullOrEmpty(methodName) ) {
				if ( act != null ) {
					Hashtable ht = act.GetValue(WorkflowMarkupSerializer.EventsProperty) as Hashtable;
					if ( ht != null ) {
						methodName = ht[e.DisplayName] as string;
					}
				}
			}
			MethodInfo mi = e.EventType.GetMethod("Invoke");
			foreach ( CodeNamespace codeNamespace in ccu.Namespaces ) {
				foreach ( CodeTypeDeclaration codeType in codeNamespace.Types ) {
					foreach ( CodeTypeMember member in codeType.Members ) {
						CodeMemberMethod method = member as CodeMemberMethod;
						if ( method != null ) {
							bool sameName = ( method.Name == methodName );
							bool sameReturnType =
								( method.ReturnType != null && method.ReturnType.BaseType == mi.ReturnType.FullName );
							bool sameParameters = false;
							ParameterInfo[] pinfos = mi.GetParameters();
							if ( pinfos.Length == method.Parameters.Count ) {
								sameParameters = true;
								for ( int i = 0; i < pinfos.Length; i++ ) {
									if ( pinfos[i].ParameterType.FullName != method.Parameters[i].Type.BaseType ) {
										sameParameters = false;
										break;
									}
								}
							}
							if ( sameName && sameReturnType && sameParameters ) {
								return method;
							}
						}
					}
				}
			}
			return null;
		}

		private class EventPropertyDescriptor : PropertyDescriptor
		{
			private EventDescriptor eventDescriptor;
			private IServiceProvider serviceProvider;
			private EventBindingService eventSvc;
			private TypeConverter converter;
			private bool useMethodCalled = false;

			public EventDescriptor EventDescriptor
			{
				get
				{
					return this.eventDescriptor;
				}
			}

			public EventPropertyDescriptor(EventDescriptor eventDesc, EventBindingService eventSvc, IServiceProvider serviceProvider)
				: base(eventDesc, null)
			{
				this.eventDescriptor = eventDesc;
				this.eventSvc = eventSvc;
				this.serviceProvider = serviceProvider;
			}

			public override Type ComponentType
			{
				get
				{
					return this.eventDescriptor.ComponentType;
				}
			}
			public override Type PropertyType
			{
				get
				{
					return this.eventDescriptor.EventType;
				}
			}

			public override TypeConverter Converter
			{
				get
				{
					if ( this.converter == null )
						this.converter = new XomlEventConverter(this.eventDescriptor);

					return this.converter;
				}
			}

			public override bool IsReadOnly
			{
				get
				{
					ReadOnlyAttribute roa = this.eventDescriptor.Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;
					if ( roa != null ) {
						return roa.IsReadOnly;
					} else {
						return false;
					}
				}
			}

			public override bool CanResetValue(object component)
			{
				return false;
			}

			public override object GetValue(object component)
			{
				string value = null;
				DependencyObject dependencyObject = component as DependencyObject;
				if ( dependencyObject != null ) {
					Hashtable dynamicEvents = dependencyObject.GetValue(WorkflowMarkupSerializer.EventsProperty) as Hashtable;
					if ( dynamicEvents != null ) {
						if ( dynamicEvents.ContainsKey(this.eventDescriptor.Name) )
							value = dynamicEvents[this.eventDescriptor.Name] as string;
					} else {
						DependencyProperty dependencyEvent = DependencyProperty.FromName(this.eventDescriptor.Name, dependencyObject.GetType());
						MethodInfo getInvocationListMethod = dependencyObject.GetType().GetMethod("System.Workflow.ComponentModel.IDependencyObjectAccessor.GetInvocationList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
						if ( getInvocationListMethod != null && dependencyEvent != null && dependencyEvent.IsEvent ) {
							MethodInfo boundGetInvocationListMethod = getInvocationListMethod.MakeGenericMethod(new Type[] { dependencyEvent.PropertyType });
							if ( boundGetInvocationListMethod != null ) {
								Delegate[] delegates = boundGetInvocationListMethod.Invoke(dependencyObject, new object[] { dependencyEvent }) as Delegate[];
								if ( delegates != null && delegates.Length > 0 && delegates[0].Method != null )
									value = delegates[0].Method.Name;
							}
						}
					}
				}

				return value;
			}

			public override void ResetValue(object component)
			{
				SetValue(component, null);
			}

			public override void SetValue(object component, object value)
			{
				if ( IsReadOnly )
					throw new ArgumentException(this.eventDescriptor.Name);

				if ( value != null && !( value is string ) )
					throw new ArgumentException(this.eventDescriptor.Name);

				if ( component is DependencyObject ) {
					string name = value as string;
					DependencyObject dependencyObject = component as DependencyObject;
					string oldName = null;
					if ( dependencyObject.GetValue(WorkflowMarkupSerializer.EventsProperty) == null )
						dependencyObject.SetValue(WorkflowMarkupSerializer.EventsProperty, new Hashtable());

					Hashtable dynamicEvents = dependencyObject.GetValue(WorkflowMarkupSerializer.EventsProperty) as Hashtable;
					if ( dynamicEvents.ContainsKey(this.eventDescriptor.Name) )
						oldName = dynamicEvents[this.eventDescriptor.Name] as string;

					if ( oldName != null && name != null && oldName.Equals(name, StringComparison.Ordinal) ) {
						foreach ( string methodName in this.eventSvc.GetCompatibleMethods(this.eventDescriptor) ) {
							if ( methodName.Equals(name, StringComparison.CurrentCulture) )
								return;
						}
					} else if ( oldName == name ) {
						return;
					}

					IDesignerHost host = this.serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost;
					if ( host == null )
						throw new InvalidOperationException(typeof(IDesignerHost).FullName);

					if ( !String.IsNullOrEmpty(name) ) {
						if ( name.StartsWith("@") )
							throw new InvalidOperationException(name);

						Activity rootActivity = host.RootComponent as Activity;
						if ( rootActivity != null ) {
							// make sure the name doesn't conflict with an existing member in the code-beside.
							MemberInfo matchingMember = null;
							Type designedType = Helpers.GetDataSourceClass(rootActivity, this.serviceProvider);
							if ( designedType != null ) {
								WorkflowLoader loader = this.serviceProvider.GetService(typeof(WorkflowLoader)) as WorkflowLoader;
								foreach ( MemberInfo memberInfo in designedType.GetMembers(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ) {
									if ( memberInfo.Name.Equals(name, StringComparison.Ordinal) ) {
										matchingMember = memberInfo;
										break;
									}
								}
							}

							if ( matchingMember != null ) {
								if ( !( matchingMember is MethodInfo ) )
									throw new InvalidOperationException(designedType.FullName);
							} else {
								// make sure the name doesn't conflict with an existing activity.
								// and name doesn't contain invalid characters.
								IIdentifierCreationService idService = this.serviceProvider.GetService(typeof(IIdentifierCreationService)) as IIdentifierCreationService;
								if ( !Helpers.ValidateIdentifier(idService, rootActivity, name) ) return;
							}
						}
					}

					// If there is a designer host, create a transaction.
					DesignerTransaction trans = null;
					if ( host != null )
						trans = host.CreateTransaction(name);

					try {
						IComponentChangeService change = this.serviceProvider.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
						if ( change != null ) {
							try {
								change.OnComponentChanging(component, this);
								change.OnComponentChanging(component, this.eventDescriptor);
							} catch ( CheckoutException coEx ) {
								if ( coEx == CheckoutException.Canceled )
									return;
								throw;
							}
						}

						if ( name != null ) {
							if ( host.RootComponent != null ) {
								if ( !this.useMethodCalled && !string.IsNullOrEmpty(oldName) )
									eventSvc.UseMethod((IComponent)component, eventDescriptor, oldName);

								eventSvc.UseMethod((IComponent)component, eventDescriptor, name);
								this.useMethodCalled = true;
							}
						}

						if ( oldName != null && host.RootComponent != null )
							eventSvc.FreeMethod((IComponent)component, eventDescriptor, oldName);

						dynamicEvents[this.eventDescriptor.Name] = name;

						if ( change != null ) {
							change.OnComponentChanged(component, this.eventDescriptor, null, null);
							change.OnComponentChanged(component, this, oldName, name);
						}

						OnValueChanged(component, EventArgs.Empty);

						if ( trans != null )
							trans.Commit();
					} finally {
						if ( trans != null )
							( (IDisposable)trans ).Dispose();
					}
				}
			}

			public override bool ShouldSerializeValue(object component)
			{
				return true;
			}

			private class XomlEventConverter : TypeConverter
			{

				private EventDescriptor evt;

				internal XomlEventConverter(EventDescriptor evt)
				{
					this.evt = evt;
				}

				public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
				{
					if ( sourceType == typeof(string) )
						return true;

					return base.CanConvertFrom(context, sourceType);
				}

				public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
				{
					if ( destinationType == typeof(string) )
						return true;

					return base.CanConvertTo(context, destinationType);
				}

				public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
				{
					if ( value == null )
						return value;

					if ( value is string ) {
						if ( ( (string)value ).Length == 0 )
							return null;

						return value;
					}
					return base.ConvertFrom(context, culture, value);
				}

				public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
				{
					if ( destinationType == typeof(string) )
						return ( value == null || !( value is string ) ) ? string.Empty : value;

					return base.ConvertTo(context, culture, value, destinationType);
				}

				public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
				{
					string[] eventMethods = null;

					if ( context != null ) {
						IEventBindingService ebs = (IEventBindingService)context.GetService(typeof(IEventBindingService));
						if ( ebs == null ) {
							if ( context.Instance is IServiceProvider ) {
							} else if ( context.PropertyDescriptor is IServiceProvider ) {
							} else if ( context.Instance is Activity ) {
								ebs = (IEventBindingService)( (Activity)context.Instance ).Site.GetService(typeof(IEventBindingService));
							}
						}
						if ( ebs != null ) {
							try {
								ICollection methods = ebs.GetCompatibleMethods(evt);
								eventMethods = new string[methods.Count];
								int i = 0;
								foreach ( string s in methods )
									eventMethods[i++] = s;
							} catch ( Exception ) {
								//Failed to get methods. return empty list
							}
						}
					}

					return new StandardValuesCollection(eventMethods);
				}

				public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
				{
					return false;
				}

				public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
				{
					return true;
				}
			}
		}

		protected override bool ShowCode(IComponent component, EventDescriptor e, string methodName)
		{
			const string tabs = "\t\t\t";
			CodeMemberMethod method = findMethod(this.loader.CodeBesideCCU, component, e, methodName);
			if ( method == null ) return false;
			FormCodeEditor frm = null;
			using ( new OpenComposite.Base.WaitMouse() ) {
				loader.Generate(true);
				frm = new FormCodeEditor(serviceProvider);
				frm.Text = e.DisplayName;
				string mname = "";
				mname += ( method.Attributes & MemberAttributes.AccessMask ).ToString().ToLower() + " ";
				mname += method.ReturnType.BaseType + " ";
				mname += method.Name + "(";
				bool isFirst = true;
				foreach ( CodeParameterDeclarationExpression pde in method.Parameters ) {
					if ( !isFirst ) mname += ", ";
					mname += pde.Type.BaseType + " " + pde.Name;
					isFirst = false;
				}
				mname += ")";
				frm.SetHeader(mname, loader.ClassName);
				// add code beside properties to syntax highlighting
				TypeProvider typeProvider = serviceProvider.GetService(typeof(ITypeProvider)) as TypeProvider;
				List<Assembly> assemblies = new List<Assembly>(typeProvider.ReferencedAssemblies);
				StringCollection namespaces = new StringCollection();
				Debug.Assert(typeProvider != null);
				foreach ( CodeNamespace codeNS in this.loader.CodeBesideCCU.Namespaces ) {
					foreach ( CodeTypeDeclaration codeType in codeNS.Types ) {
					}
					namespaces.Add(codeNS.Name);
				}
				//foreach ( Assembly assembly in typeProvider.ReferencedAssemblies ) {
				//    flags.Add(IntelliPromptNamespaceAndTypeFlags.Public);
				//}
				//frm.Members.AddReflectionForAssemblyNamespacesAndTypes(assemblies.ToArray(),
				//    flags.ToArray(),
				//    namespaces,
				//    null, false);

				if ( method.Statements.Count > 0 ) {
					CodeSnippetStatement snippet = method.Statements[0] as CodeSnippetStatement;
					if ( snippet != null ) {
						string code = snippet.Value;
						if ( code.StartsWith(tabs) ) code = code.Remove(0, tabs.Length);
						if ( code.Contains(Environment.NewLine) ) {
							code = code.Replace(Environment.NewLine + tabs, Environment.NewLine);
						} else {
							code = code.Replace("\n" + tabs, "\n");
						}
						frm.Code = code;
					}
				}
			}
			if ( frm.ShowDialog() == System.Windows.Forms.DialogResult.OK ) {
				foreach ( Repository.Workflow.MethodCodeSnippet mcs in
							loader.Workflow.ExtendedProperties.MethodCodeSnippets ) {
					if ( mcs.MethodName == method.Name ) {
						OpenComposite.Workflow.ExtendedCodeActivity codeAct = component as OpenComposite.Workflow.ExtendedCodeActivity;
						if ( codeAct != null ) {
							mcs.ActivityName = codeAct.Name;
						}
						string code = frm.InnerCode;
						code = tabs + code;
						if ( code.Contains(Environment.NewLine) ) {
							code = code.Replace(Environment.NewLine, Environment.NewLine + tabs);
						} else {
							code = code.Replace("\n", "\n" + tabs);
						}
						mcs.Code = code;
						method.Statements.Clear();
						method.Statements.Add(new CodeSnippetStatement(code));
					}
				}
			}
			return true;
		}

		#region IEventBindingService Members

		string IEventBindingService.CreateUniqueMethodName(IComponent component, EventDescriptor e)
		{
			return this.CreateUniqueMethodName(component, e);
		}

		ICollection IEventBindingService.GetCompatibleMethods(EventDescriptor e)
		{
			return this.GetCompatibleMethods(e);
		}

		EventDescriptor IEventBindingService.GetEvent(PropertyDescriptor property)
		{
			return ( property is EventPropertyDescriptor ) ? ( (EventPropertyDescriptor)property ).EventDescriptor : null;
		}

		PropertyDescriptorCollection IEventBindingService.GetEventProperties(EventDescriptorCollection events)
		{
			List<PropertyDescriptor> lstPropDescs = new List<PropertyDescriptor>();
			foreach ( EventDescriptor ed in events ) {
				lstPropDescs.Add(new EventPropertyDescriptor(ed, this, this.serviceProvider));
			}
			return new PropertyDescriptorCollection(lstPropDescs.ToArray(), true);
		}

		PropertyDescriptor IEventBindingService.GetEventProperty(EventDescriptor e)
		{
			return new EventPropertyDescriptor(e, this, this.serviceProvider);
		}

		bool IEventBindingService.ShowCode(IComponent component, EventDescriptor e)
		{
			return this.ShowCode(component, e);
		}

		bool IEventBindingService.ShowCode(int lineNumber)
		{
			return this.ShowCode(lineNumber);
		}

		bool IEventBindingService.ShowCode()
		{
			return this.ShowCode();
		}

		#endregion
	}

}
